#include "ota_admin.h"
#ifdef ENABLE_SOCKET_HTTP
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"

#include "http_request.h"

#define LOG_TAG "http"
#include "elog.h"

#define REQUEST "%s %s HTTP/1.1\r\n" \
                "Host: %s\r\n"       \
                "User-Agent: http/1.0 TTG\r\n"

int8_t http_request(const char *method, char *server, char *port, char *path, const char *data, uint32_t data_len, http_stream_cb_t cb, void *user_cb, bool need_header))
{
#define RECV_DATA_SIZE 4096
    if (!server || !port || !path)
        return 0;
    char *buffer = malloc(RECV_DATA_SIZE);
    if (!buffer)
        return 0;
    const struct addrinfo hints = {
        .ai_family = AF_INET,
        .ai_socktype = SOCK_STREAM,
    };
    struct addrinfo *res;
    struct in_addr *addr;
    int s, r = 0;

    int err = getaddrinfo(server, port, &hints, &res);

    if (err != 0 || res == NULL)
    {
        printf("DNS lookup failed err=%d res=%p\n", err, res);
        free(buffer);
        return 0;
    }

    /* Code to print the resolved IP.
       Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code */
    addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
    printf("DNS lookup succeeded. IP=%s\n", inet_ntoa(*addr));

    s = socket(res->ai_family, res->ai_socktype, 0);
    if (s < 0)
    {
        printf("... Failed to allocate socket.\n");
        freeaddrinfo(res);
        goto http_get_err;
    }
    printf("... allocated socket\n");

    if (connect(s, res->ai_addr, res->ai_addrlen) != 0)
    {
        printf("... socket connect failed errno=%d\n", errno);
        goto http_get_err;
    }

    printf("... connected\n");
    freeaddrinfo(res);

    uint32_t header_len = sprintf(buffer, REQUEST, method, path, server);
    if (data)
    {
        header_len += sprintf(&buffer[header_len], "Content-Type: application/json\r\n");
        header_len += sprintf(&buffer[header_len], "Content-Length: %ld\r\n", data_len);
        header_len += sprintf(&buffer[header_len], "\r\n");
        header_len += sprintf(&buffer[header_len], "%s", data);
        header_len += sprintf(&buffer[header_len], "\r\n\r\n");
    }
    else
        header_len += sprintf(&buffer[header_len], "\r\n");
    printf("... sending HTTP request...\n");
    // elog_raw(request);

    if (write(s, buffer, header_len) < 0)
    {
        printf("... socket send failed\n");
        goto http_get_err;
    }
    printf("... socket send success\n");

    /* Read HTTP response */
    uint8_t timeout_count = 0;
    uint8_t check_header = 0;
    uint32_t data_size = 0xFFFFFFFF;
    do
    {
        bzero(buffer, RECV_DATA_SIZE);
        struct timeval tv = {
            .tv_sec = 1,
            .tv_usec = 0,
        };
        fd_set rfds;
        FD_ZERO(&rfds);
        FD_SET(s, &rfds);

        int sock = select(s + 1, &rfds, NULL, NULL, &tv);
        if (sock < 0)
        {
            printf("Select failed: errno %d\n", errno);
            err = -1;
            break;
        }
        else if (sock > 0)
        {
            if (FD_ISSET(s, &rfds))
            {
                struct sockaddr_storage raddr; // Large enough for both IPv4 or IPv6
                socklen_t socklen = sizeof(raddr);
                r = recvfrom(s, buffer, RECV_DATA_SIZE, 0, (struct sockaddr *)&raddr, &socklen);
                if (r)
                {
                    char *data = buffer;
                    if (!check_header)
                    {
                        char *content = buffer;
                        printf("... received HTTP response...\n");
                        while ((content = strstr(content, "Content-")))
                        {
                            check_header = 1;
                            char *p = content;
                            if (strstr(p, "Length:"))
                                data_size = atol(p + 15);
                            content = strstr(content, "\n") + 1;
                            printf("%.*s", (int)(content - p), p);
                        }
                        data = strstr(buffer, "\r\n\r\n");
                        if (data)
                        {
                            check_header = 1;
                            data[0] = '\0';
                            data += 4;
                            r -= (data - buffer);
                        }
                    }
                    data_size -= r;
                    if (cb)
                    {
                        err = cb(user_cb, (uint8_t *)data, r);
                        if ((uint8_t)err)
                        {
                            printf("User cb close.\n");
                            goto http_get_err;
                        }
                    }
                    else
                    {
                        printf("No find User cb.\n");
                        buffer[r] = '\0';
                        for (int i = 0; i < r; i++)
                            putchar(buffer[i]);
                    }
                    if (data_size == 0)
                        goto http_request_done;
                }
                else if (!r)
                {
                    err = 1;
                    break;
                }
            }
        }
        else
        {
            printf("Receive a timeout\n");
            if (++timeout_count > 5)
                break;
            continue;
        }
    } while (r > 0);

http_request_done:
    if (cb)
        cb(user_cb, 0, 0); /* read done */

    printf("... done reading from socket. Last read return=%d errno=%d.\n", r, errno);
    free(buffer);
    close(s);

    return err;
http_get_err:
    free(buffer);
    close(s);
    return err - 1;
}

int8_t http_get(char *server, char *port, char *path, const char *data, uint32_t data_len, http_stream_cb_t cb, void *user_cb, bool need_header))
{
    return http_request("GET", server, port, path, data, data_len, cb, user_cb, need_header);
}

int8_t http_post(char *server, char *port, char *path, const char *data, uint32_t data_len, http_stream_cb_t cb, void *user_cb, bool need_header))
{
    return http_request("POST", server, port, path, data, data_len, cb, user_cb, need_header);
}

int8_t http_request_from_url(const char *method, char *url, const char *data, uint32_t data_len, http_stream_cb_t cb, void *user_cb, bool need_header))
{
    if (!url || !cb)
        return -1;
    uint8_t len;
    char *p, *p2;
    char server[256] = "127.0.0.1";
    char port[6] = "80";
    char path[256] = "/";
    char *source_url = url;
    url = strstr(source_url, "http://");
    if (!url)
    {
        url = strstr(source_url, "https://");
        if (url)
            url += 8;
        else
            url = source_url;
    }
    else
    {
        url += 7;
    }

    if (strchr(url, ':'))
    {
        p = strchr(url, ':');
        len = p - url;
        p++;
        if (strchr(p, '/'))
        {
            p2 = strchr(p, '/');
            strncpy(port, p, p2 - p);
            strcpy(path, p2);
        }
        else
            strncpy(port, p, sizeof(port) - 1);
    }
    else if (strchr(url, '/'))
    {
        p2 = strchr(url, '/');
        len = p2 - url;
        strcpy(path, p2);
    }
    else
        len = strlen(url);
    strncpy(server, url, len);
    return http_request(method, server, port, path, data, data_len, cb, user_cb, need_header);
}
#endif